#include "sdk.h"
#include <wx/tooltip.h>
#include <wx/wupdlock.h>

bool M_Code_AuiNotebook::s_AllowMousewheel = true;
cbAuiNotebookArray M_Code_AuiNotebook::s_cbAuiNotebookArray;
wxString M_Code_AuiNotebook::s_modKeys = _T( "Ctrl" );
bool M_Code_AuiNotebook::s_modToAdvance = false;
int M_Code_AuiNotebook::s_advanceDirection = 1;
int M_Code_AuiNotebook::s_moveDirection = 1;

BEGIN_EVENT_TABLE( M_Code_AuiNotebook, wxAuiNotebook )
  EVT_NAVIGATION_KEY( M_Code_AuiNotebook::OnNavigationKey )
  EVT_IDLE( M_Code_AuiNotebook::OnIdle )
  EVT_AUINOTEBOOK_DRAG_DONE( wxID_ANY, M_Code_AuiNotebook::OnDragDone )
END_EVENT_TABLE()

M_Code_AuiNotebook::M_Code_AuiNotebook( wxWindow* pParent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
  : wxAuiNotebook( pParent, id, pos, size, style ),
    m_HasToolTip( false ),
    m_SetZoomOnIdle( false ),
    m_MinimizeFreeSpaceOnIdle( false ),
    m_TabCtrlSize( wxDefaultSize ) {
  m_mgr.SetFlags( ( m_mgr.GetFlags() | wxAUI_MGR_VENETIAN_BLINDS_HINT ) & ~wxAUI_MGR_TRANSPARENT_HINT );
  ConfigManager *cfg = Manager::Get()->GetConfigManager( _T( "app" ) );
  s_AllowMousewheel = cfg->ReadBool( _T( "/environment/tabs_use_mousewheel" ), true );
  s_modKeys = cfg->Read( _T( "/environment/tabs_mousewheel_modifier" ), _T( "Ctrl" ) );
  s_modToAdvance = cfg->ReadBool( _T( "/environment/tabs_mousewheel_advance" ), false );
  M_Code_AuiNotebook::InvertAdvanceDirection( cfg->ReadBool( _T( "/environment/tabs_invert_advance" ), false ) );
  M_Code_AuiNotebook::InvertMoveDirection( cfg->ReadBool( _T( "/environment/tabs_invert_move" ), false ) );
  if( s_cbAuiNotebookArray.Index( this ) == wxNOT_FOUND ) {
    s_cbAuiNotebookArray.Add( this );
  }
}

M_Code_AuiNotebook::~M_Code_AuiNotebook() {
  s_cbAuiNotebookArray.Remove( this );
}

bool M_Code_AuiNotebook::CheckKeyModifier() {
  bool result = true;
  wxString str = s_modKeys;
  str.MakeUpper();
  if( result && str.Contains( _T( "ALT" ) ) ) {
    result = wxGetKeyState( WXK_ALT );
  }
  if( result && str.Contains( _T( "CTRL" ) ) ) {
    result = wxGetKeyState( WXK_CONTROL );
  }
  if( result && str.Contains( _T( "SHIFT" ) ) ) {
    result = wxGetKeyState( WXK_SHIFT );
  }
  return result;
}

void M_Code_AuiNotebook::UpdateTabControlsArray() {
  cbAuiTabCtrlArray saveTabCtrls = m_TabCtrls;
  m_TabCtrls.Clear();
  const size_t tab_Count = GetPageCount();
  for( size_t i = 0; i < tab_Count; ++i ) {
    wxAuiTabCtrl* tabCtrl = nullptr;
    int idx = -1;
    if( FindTab( GetPage( i ), &tabCtrl, &idx ) ) {
      if( tabCtrl && m_TabCtrls.Index( tabCtrl ) == wxNOT_FOUND ) {
        m_TabCtrls.Add( tabCtrl );
      }
    } else
    { continue; }
  }
  bool needEventRebind = m_TabCtrls.GetCount() != saveTabCtrls.GetCount();
  if( !needEventRebind ) {
    for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
      if( saveTabCtrls.Index( m_TabCtrls[i] ) == wxNOT_FOUND ) {
        needEventRebind = true;
        break;
      }
    }
  }
  if( needEventRebind ) {
    ResetTabCtrlEvents();
  }
  for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
    int pageCount = m_TabCtrls[i]->GetPageCount();
    if( m_TabCtrls[i]->GetActivePage() < 0 && pageCount > 0 ) {
      m_TabCtrls[i]->SetActivePage( ( size_t )0 );
    }
    if( m_TabCtrls[i]->GetActivePage() >= pageCount && pageCount > 0 ) {
      m_TabCtrls[i]->SetActivePage( pageCount - 1 );
    }
    m_TabCtrls[i]->DoShowHide();
  }
}

void M_Code_AuiNotebook::ResetTabCtrlEvents() {
  for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
    m_TabCtrls[i]->Disconnect( wxEVT_MOTION, wxMouseEventHandler( M_Code_AuiNotebook::OnMotion ) );
    m_TabCtrls[i]->Connect( wxEVT_MOTION, wxMouseEventHandler( M_Code_AuiNotebook::OnMotion ) );
    m_TabCtrls[i]->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( M_Code_AuiNotebook::OnTabCtrlDblClick ) );
    m_TabCtrls[i]->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( M_Code_AuiNotebook::OnTabCtrlDblClick ) );
    m_TabCtrls[i]->Disconnect( wxEVT_SIZE, wxSizeEventHandler( M_Code_AuiNotebook::OnResize ) );
    m_TabCtrls[i]->Connect( wxEVT_SIZE, wxSizeEventHandler( M_Code_AuiNotebook::OnResize ) );
    m_TabCtrls[i]->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( M_Code_AuiNotebook::OnTabCtrlMouseWheel ) );
    if( GetPageCount() > 1 ) {
      if( s_AllowMousewheel ) {
        m_TabCtrls[i]->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( M_Code_AuiNotebook::OnTabCtrlMouseWheel ) );
      }
    }
  }
}

void M_Code_AuiNotebook::FocusActiveTabCtrl() {
  UpdateTabControlsArray();
  int sel = GetSelection();
  if( sel < 0 ) {
    return;
  }
  wxWindow* wnd = GetPage( static_cast<size_t>( sel ) );
  if( !wnd ) {
    return;
  }
  for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
    wxWindow* win = m_TabCtrls[i]->GetWindowFromIdx( m_TabCtrls[i]->GetActivePage() );
    if( win && ( win == wnd ) ) {
      m_TabCtrls[i]->SetFocus();
      break;
    }
  }
}

wxAuiTabCtrl* M_Code_AuiNotebook::GetTabCtrl( wxWindow *page ) {
  if( page ) {
    wxAuiTabCtrl *tabCtrl = nullptr;
    int idx;
    if( !FindTab( page, &tabCtrl, &idx ) ) {
      return nullptr;
    }
    return tabCtrl;
  } else {
    return GetActiveTabCtrl();
  }
}

void M_Code_AuiNotebook::GetPagesInTabCtrl( std::vector<wxWindow*> &result, wxWindow *page ) {
  result.clear();
  wxAuiTabCtrl* tabCtrl = GetTabCtrl( page );
  if( tabCtrl ) {
    int count = tabCtrl->GetPageCount();
    for( int ii = 0; ii < count; ++ii ) {
      result.push_back( tabCtrl->GetPage( ii ).window );
    }
  }
}

void M_Code_AuiNotebook::SetZoom( int zoom ) {
  UpdateTabControlsArray();
  for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
    wxWindow* win = m_TabCtrls[i]->GetWindowFromIdx( m_TabCtrls[i]->GetActivePage() );
    if( win && static_cast<EditorBase*>( win )->IsBuiltinEditor() ) {
      static_cast<M_Code_Editor*>( win )->SetZoom( zoom );
    }
  }
  m_SetZoomOnIdle = true;
}

void M_Code_AuiNotebook::OnIdle( M_Code_unused wxIdleEvent& event ) {
  if( m_SetZoomOnIdle ) {
    m_SetZoomOnIdle = false;
    int zoom = Manager::Get()->GetEditorManager()->GetZoom();
    for( size_t i = 0; i < GetPageCount(); ++i ) {
      wxWindow* win = GetPage( i );
      if( win && static_cast<EditorBase*>( win )->IsBuiltinEditor() ) {
        static_cast<M_Code_Editor*>( win )->SetZoom( zoom );
      }
    }
  }
  if( m_MinimizeFreeSpaceOnIdle ) {
    m_MinimizeFreeSpaceOnIdle = false;
    UpdateTabControlsArray();
    for( size_t i = 0; i < m_TabCtrls.GetCount(); ++i ) {
      MinimizeFreeSpace( m_TabCtrls[i] );
    }
  }
}

void M_Code_AuiNotebook::OnDragDone( M_Code_unused wxAuiNotebookEvent& event ) {
  UpdateTabControlsArray();
}

void M_Code_AuiNotebook::OnTabCtrlDblClick( wxMouseEvent& event ) {
  wxWindow* win = nullptr;
  wxAuiTabCtrl* tabCtrl = ( wxAuiTabCtrl* )event.GetEventObject();
  if( tabCtrl && tabCtrl->TabHitTest( event.GetX(), event.GetY(), &win ) ) {
    if( win != nullptr ) {
      CodeBlocksEvent theEvent( cbEVT_CBAUIBOOK_LEFT_DCLICK, GetParent()->GetId() );
      theEvent.SetEventObject( win );
      GetParent()->GetEventHandler()->ProcessEvent( theEvent );
    }
  }
}

void M_Code_AuiNotebook::OnTabCtrlMouseWheel( wxMouseEvent& event ) {
  wxAuiTabCtrl* tabCtrl = ( wxAuiTabCtrl* )event.GetEventObject();
  if( !tabCtrl ) {
    return;
  }
  M_Code_AuiNotebook* nb = ( M_Code_AuiNotebook* )tabCtrl->GetParent();
  if( !nb ) {
    return;
  }
  nb->SetSelection( nb->GetPageIndex( tabCtrl->GetWindowFromIdx( tabCtrl->GetActivePage() ) ) );
  bool modkeys = CheckKeyModifier();
  bool advance = ( !s_modToAdvance && !modkeys ) || ( s_modToAdvance &&  modkeys );
  if( advance ) {
    nb->AdvanceSelection( ( event.GetWheelRotation() * s_advanceDirection ) < 0 );
  } else {
    size_t tabOffset = tabCtrl->GetTabOffset();
    size_t lastTabIdx = tabCtrl->GetPageCount() - 1;
    wxWindow* win = nb->GetPage( nb->GetSelection() );
    if( win ) {
      wxClientDC dc( win );
      if( ( event.GetWheelRotation() * s_moveDirection ) > 0 ) {
        if( !tabCtrl->IsTabVisible( lastTabIdx, tabOffset, &dc, win ) ) {
          tabOffset++;
        }
      } else {
        if( tabOffset > 0 ) {
          tabOffset--;
        }
      }
      tabCtrl->SetTabOffset( tabOffset );
      nb->Refresh();
    }
  }
}

void M_Code_AuiNotebook::OnResize( wxSizeEvent& event ) {
  wxAuiTabCtrl* tabCtrl = ( wxAuiTabCtrl* )event.GetEventObject();
  if( tabCtrl ) {
    M_Code_AuiNotebook* nb = ( M_Code_AuiNotebook* )tabCtrl->GetParent();
    if( nb ) {
      if( nb->m_TabCtrlSize != event.GetSize() ) {
        nb->m_TabCtrlSize = event.GetSize();
        nb->MinimizeFreeSpace();
      }
    }
  }
  event.Skip();
}

void M_Code_AuiNotebook::OnMotion( wxMouseEvent& event ) {
  event.Skip();
  wxAuiTabCtrl* tabCtrl = ( wxAuiTabCtrl* )event.GetEventObject();
  if( !tabCtrl ) {
    return;
  }
  M_Code_AuiNotebook* nb = ( M_Code_AuiNotebook* )tabCtrl->GetParent();
  if( !nb || !nb->m_HasToolTip ) {
    return;
  }
  wxWindow* win = nullptr;
  if( event.Moving() && tabCtrl->TabHitTest( event.m_x, event.m_y, &win ) ) {
    if( !win ) {
      tabCtrl->UnsetToolTip();
      return;
    }
    wxString text( win->GetName() );
    wxToolTip* tooltip = tabCtrl->GetToolTip();
    if( !tooltip || tooltip->GetTip() != text ) {
      tabCtrl->SetToolTip( text );
    }
  } else {
    tabCtrl->UnsetToolTip();
  }
}

bool M_Code_AuiNotebook::SetPageToolTip( size_t idx, const wxString & text ) {
  if( !m_HasToolTip ) {
    UpdateTabControlsArray();
  }
  m_HasToolTip = true;
  wxWindow* win = GetPage( idx );
  if( win && win->GetName() != text ) {
    win->SetName( text );
  } else {
    return false;
  }
  return true;
}
wxString M_Code_AuiNotebook::GetPageToolTip( size_t idx ) {
  wxWindow* win = GetPage( idx );
  if( win ) {
    return win->GetName();
  }
  return wxEmptyString;
}

void M_Code_AuiNotebook::MinimizeFreeSpace() {
  if( GetPageCount() < 2 ) {
    return;
  }
  m_MinimizeFreeSpaceOnIdle = true;
}

void M_Code_AuiNotebook::MinimizeFreeSpace( wxAuiTabCtrl* tabCtrl ) {
  if( !tabCtrl || tabCtrl->GetPageCount() < 2 || !IsWindowReallyShown( this ) ) {
    return;
  }
  int ctrl_idx = tabCtrl->GetActivePage();
  wxWindow* win = GetPage( ctrl_idx );
  if( win ) {
    int tabOffset = tabCtrl->GetTabOffset();
    wxClientDC dc( win );
    size_t lastTabIdx = tabCtrl->GetPageCount() - 1;
    if( !tabCtrl->IsTabVisible( ctrl_idx, tabOffset, & dc, win ) ) {
      for( int i = lastTabIdx ; i >= 0; --i ) {
        if( tabCtrl->IsTabVisible( ctrl_idx, i, & dc, win ) ) {
          tabOffset = i;
          break;
        }
      }
    }
    while( tabOffset > 0 && tabCtrl->IsTabVisible( lastTabIdx, tabOffset - 1, & dc, win ) ) {
      --tabOffset;
    }
    tabCtrl->SetTabOffset( tabOffset );
  }
  tabCtrl->Refresh();
}

bool M_Code_AuiNotebook::DeletePage( size_t page ) {
  bool result = wxAuiNotebook::DeletePage( page );
  MinimizeFreeSpace();
  return result;
}

bool M_Code_AuiNotebook::RemovePage( size_t page ) {
  bool result = wxAuiNotebook::RemovePage( page );
  MinimizeFreeSpace();
  return result;
}

bool M_Code_AuiNotebook::MovePage( wxWindow* page, size_t new_idx ) {
  UpdateTabControlsArray();
  bool result = false;
  if( m_TabCtrls.GetCount() > 0 ) {
    result = m_TabCtrls[0]->MovePage( page, new_idx );
    Refresh();
    MinimizeFreeSpace();
  }
  return result;
}

bool M_Code_AuiNotebook::AddPage( wxWindow* page, const wxString& caption, bool select, const wxBitmap& bitmap ) {
  bool result = wxAuiNotebook::AddPage( page, caption, select, bitmap );
  MinimizeFreeSpace();
  return result;
}

bool M_Code_AuiNotebook::InsertPage( size_t page_idx, wxWindow* page, const wxString& caption, bool select, const wxBitmap& bitmap ) {
  bool result = wxAuiNotebook::InsertPage( page_idx, page, caption, select, bitmap );
  MinimizeFreeSpace();
  return result;
}

int M_Code_AuiNotebook::GetTabPositionFromIndex( int index ) {
  if( GetPageCount() <= 0 ) {
    return wxNOT_FOUND;
  }
  UpdateTabControlsArray();
  wxAuiTabCtrl* tabCtrl = nullptr;
  int idx = -1;
  if( !FindTab( GetPage( index ), &tabCtrl, &idx ) ) {
    return wxNOT_FOUND;
  }
  if( !tabCtrl || idx < 0 ) {
    return wxNOT_FOUND;
  }
  int indexOffset = 0;
  wxAuiPaneInfoArray& all_panes = m_mgr.GetAllPanes();
  const size_t pane_count = all_panes.GetCount();
  for( size_t i = 0; i < pane_count; ++i ) {
    wxAuiPaneInfo& pane = all_panes[i];
    if( pane.name == _T( "dummy" ) ) {
      continue;
    }
    if( pane.window == GetTabFrameFromTabCtrl( tabCtrl ) ) {
      break;
    }
    for( size_t j = 0; j < m_TabCtrls.GetCount(); ++j ) {
      if( pane.window == GetTabFrameFromTabCtrl( m_TabCtrls[j] ) ) {
        indexOffset += m_TabCtrls[j]->GetPageCount();
        break;
      }
    }
  }
  return idx + indexOffset;
}

void M_Code_AuiNotebook::AdvanceSelection( bool forward ) {
  if( GetPageCount() <= 1 ) {
    return;
  }
  int currentSelection = GetSelection();
  wxAuiTabCtrl* tabCtrl = nullptr;
  int idx = -1;
  if( !FindTab( GetPage( currentSelection ), &tabCtrl, &idx ) ) {
    return;
  }
  if( !tabCtrl || idx < 0 ) {
    return;
  }
  wxWindow* page = nullptr;
  size_t maxPages = tabCtrl->GetPageCount();
  forward ? idx++ : idx--;
  if( idx < 0 ) {
    idx = maxPages - 1;
  }
  if( ( size_t )idx < maxPages ) {
    page = tabCtrl->GetPage( idx ).window;
  }
  if( !page && maxPages > 0 ) {
    page = tabCtrl->GetPage( 0 ).window;
  }
  if( page ) {
    currentSelection = GetPageIndex( page );
    SetSelection( currentSelection );
  }
}

void M_Code_AuiNotebook::OnNavigationKey( wxNavigationKeyEvent& event ) {
  if( event.IsWindowChange() ) {
    AdvanceSelection( event.GetDirection() );
  } else {
    wxAuiNotebook::OnNavigationKey( event );
  }
}

wxString M_Code_AuiNotebook::SavePerspective( const wxString projectTitle ) {
  wxString tabs, tabsTmp;
  wxArrayString panes;
  UpdateTabControlsArray();
  wxAuiPaneInfoArray& all_panes = m_mgr.GetAllPanes();
  const size_t pane_count = all_panes.GetCount();
  for( size_t i = 0; i < pane_count; ++i ) {
    wxAuiPaneInfo& pane = all_panes.Item( i );
    if( pane.name == _T( "dummy" ) ) {
      continue;
    }
    wxAuiTabCtrl* tabCtrl = nullptr;
    for( size_t j = 0; j < m_TabCtrls.GetCount(); ++j ) {
      if( pane.window == GetTabFrameFromTabCtrl( m_TabCtrls.Item( j ) ) ) {
        tabCtrl = m_TabCtrls.Item( j );
        break;
      }
    }
    if( tabCtrl ) {
      tabsTmp.Clear();
      size_t page_count = tabCtrl->GetPageCount();
      for( size_t p = 0; p < page_count; ++p ) {
        wxAuiNotebookPage& page = tabCtrl->GetPage( p );
        const size_t page_idx = m_tabs.GetIdxFromWindow( page.window );
        wxString id = UniqueIdFromTooltip( GetPageToolTip( page_idx ) );
        if( id.BeforeLast( ':' ).empty() ) {
          continue;
        }
        if( !projectTitle.empty() && id.BeforeLast( ':' ) != projectTitle ) {
          continue;
        }
        if( !tabsTmp.empty() ) {
          tabsTmp += _T( "," );
        }
        if( ( int )page_idx == m_curpage ) {
          tabsTmp += _T( "*" );
        } else if( ( int )p == tabCtrl->GetActivePage() ) {
          tabsTmp += _T( "+" );
        }
        tabsTmp += wxString::Format( _T( "%lu" ), static_cast<unsigned long>( page_idx ) );
        tabsTmp += _T( ";" );
        tabsTmp += id;
      }
      if( !tabsTmp.empty() ) {
        if( !tabs.empty() ) {
          tabs += _T( "|" );
        }
        panes.Add( pane.name );
        tabs += pane.name;
        tabs += _T( "=" );
        tabs += tabsTmp;
      }
    }
  }
  tabs += _T( "@" );
  tabsTmp = m_mgr.SavePerspective();
  wxArrayString arTabsTmp = GetArrayFromString( tabsTmp, _T( "|" ) );
  for( size_t i = arTabsTmp.GetCount(); i > 0 ; ) {
    if( arTabsTmp.Item( --i ).StartsWith( _T( "name=" ) ) ) {
      wxString strTmp = arTabsTmp.Item( i ).AfterFirst( '=' ).BeforeFirst( ';' );
      if( strTmp == _T( "dummy" ) ) {
        continue;
      }
      if( panes.Index( strTmp ) < 0 ) {
        arTabsTmp.RemoveAt( i );
      }
    }
  }
  tabsTmp = GetStringFromArray( arTabsTmp, _T( "|" ) );
  tabs += tabsTmp;
  return tabs;
}

wxString M_Code_AuiNotebook::UniqueIdFromTooltip( const wxString& text ) {
  ProjectFile* pf = nullptr;
  M_Code_Project* prj = nullptr;
  wxString id =  _T( "" );
  wxString fn = text.BeforeFirst( _T( '\n' ) );
  prj = Manager::Get()->GetProjectManager()->FindProjectForFile( fn, &pf, false, true );
  if( prj && pf ) {
    id = prj->GetTitle() + _T( ':' ) + pf->relativeFilename;
  }
  return id;
}

int M_Code_AuiNotebook::GetTabIndexFromTooltip( const wxString& text ) {
  if( text == _T( "" ) ) {
    return -1;
  }
  for( size_t i = 0; i < m_tabs.GetPageCount(); ++i ) {
    if( UniqueIdFromTooltip( GetPageToolTip( i ) ) == text ) {
      return i;
    }
  }
  return -1;
}

bool M_Code_AuiNotebook::LoadPerspective( const wxString& layout, bool mergeLayouts ) {
  if( layout.IsEmpty() ) {
    return false;
  }
  wxString tabs = layout.BeforeFirst( _T( '@' ) );
  const size_t tab_count = m_tabs.GetPageCount();
  for( size_t i = 0; i < tab_count; ++i ) {
    if( tabs.Find( UniqueIdFromTooltip( GetPageToolTip( i ) ) ) < 0 ) {
      continue;
    }
    wxWindow* wnd = m_tabs.GetWindowFromIdx( i );
    wxAuiTabCtrl* ctrl;
    int ctrl_idx;
    if( !FindTab( wnd, &ctrl, &ctrl_idx ) ) {
      return false;
    }
    if( !ctrl->RemovePage( wnd ) ) {
      return false;
    }
  }
  RemoveEmptyTabFrames();
  wxString currentLayout;
  if( mergeLayouts ) {
    currentLayout = m_mgr.SavePerspective();
    wxString tempLayout;
    while( !currentLayout.empty() ) {
      if( currentLayout.BeforeFirst( '|' ).StartsWith( _T( "layout2" ) ) ||
          currentLayout.BeforeFirst( '|' ).StartsWith( _T( "name=dummy" ) ) ) {
        currentLayout = currentLayout.AfterFirst( ( '|' ) );
        currentLayout.Trim();
        currentLayout.Trim( true );
      } else {
        wxString pane_part = currentLayout.BeforeFirst( '|' );
        pane_part.Trim();
        pane_part.Trim( true );
        if( !pane_part.empty() ) {
          tempLayout += pane_part + _T( "|" );
        }
        currentLayout = currentLayout.AfterFirst( '|' );
        currentLayout.Trim();
        currentLayout.Trim( true );
      }
    }
    currentLayout = tempLayout;
    if( currentLayout.empty() ) {
      mergeLayouts = false;
    }
  }
  size_t sel_page = 0;
  int active_tab = 0;
  bool found = false;
  wxString frames = layout.AfterFirst( _T( '@' ) );
  bool firstTabInCtrl = ! currentLayout.empty();
  wxAuiTabCtrl* dest_tabs = GetActiveTabCtrl();
  while( 1 ) {
    const wxString tab_part = tabs.BeforeFirst( _T( '|' ) );
    if( tab_part.empty() ) {
      break;
    }
    wxString pane_name = tab_part.BeforeFirst( _T( '=' ) );
    m_curpage = -1;
    wxString tab_list = tab_part.AfterFirst( _T( '=' ) );
    while( 1 ) {
      wxString tab = tab_list.BeforeFirst( _T( ',' ) );
      wxString name = tab.AfterFirst( _T( ';' ) );
      tab = tab.BeforeFirst( _T( ';' ) );
      if( tab.empty() ) {
        break;
      }
      tab_list = tab_list.AfterFirst( _T( ',' ) );
      const wxChar c = tab[0];
      if( c == _T( '+' ) || c == _T( '*' ) ) {
        tab = tab.Mid( 1 );
      }
      const int index_in_m_tabs = GetTabIndexFromTooltip( name );
      if( index_in_m_tabs < 0 ) {
        continue;
      }
      wxAuiNotebookPage& page = m_tabs.GetPage( index_in_m_tabs );
      active_tab = dest_tabs->GetActivePage();
      const size_t newpage_idx = dest_tabs->GetPageCount();
      dest_tabs->InsertPage( page.window, page, newpage_idx );
      if( c == _T( '+' ) ) {
        dest_tabs->SetActivePage( newpage_idx );
      } else if( c == _T( '*' ) ) {
        sel_page = index_in_m_tabs;
      }
      if( firstTabInCtrl ) {
        int nextIndex = m_TabCtrls.Index( dest_tabs ) + 1;
        if( nextIndex == 0 || nextIndex >= static_cast<int>( m_TabCtrls.GetCount() ) ) {
          Split( index_in_m_tabs, wxRIGHT );
          dest_tabs->SetActivePage( active_tab );
          dest_tabs = GetActiveTabCtrl();
        } else
        { dest_tabs = m_TabCtrls.Item( nextIndex ); }
      }
      wxAuiPaneInfo& pane = m_mgr.GetPane( GetTabFrameFromTabCtrl( dest_tabs ) );
      if( pane.name != pane_name ) {
        tab.Replace( pane_name, pane.name );
        frames.Replace( pane_name, pane.name );
        pane_name = pane.name;
      }
      firstTabInCtrl = false;
      found = true;
    }
    firstTabInCtrl = true;
    tabs = tabs.AfterFirst( _T( '|' ) );
  }
  for( size_t i = 0; i < tab_count; ++i ) {
    wxAuiNotebookPage& page = m_tabs.GetPage( i );
    wxAuiTabCtrl* ctrl;
    int ctrl_idx;
    if( !FindTab( page.window, &ctrl, &ctrl_idx ) ) {
      const size_t newpage_idx = dest_tabs->GetPageCount();
      dest_tabs->InsertPage( page.window, page, newpage_idx );
    }
  }
  if( mergeLayouts ) {
    wxRegEx reDockSize( _T( "(dock_size[()0-9,]+=)[0-9]+" ) );
    const wxString replacement( _T( "\\1-1" ) );
    frames.Replace( F( _T( "dock_size(%d" ), wxAUI_DOCK_CENTER ), F( _T( "dock_size(%d" ), wxAUI_DOCK_LEFT ) );
    frames.Replace( F( _T( "dir=%d" ), wxAUI_DOCK_CENTER ), F( _T( "dir=%d" ), wxAUI_DOCK_LEFT ) );
    if( reDockSize.Matches( frames ) ) {
      reDockSize.ReplaceAll( &frames, replacement );
    }
    if( reDockSize.Matches( currentLayout ) ) {
      reDockSize.ReplaceAll( &currentLayout, replacement );
    }
    while( !currentLayout.empty() ) {
      wxString pane_part = currentLayout.BeforeFirst( '|' );
      pane_part.Trim();
      pane_part.Trim( true );
      if( !pane_part.empty() ) {
        frames += pane_part + _T( "|" );
      }
      currentLayout = currentLayout.AfterFirst( '|' );
      currentLayout.Trim();
      currentLayout.Trim( true );
    }
  }
  if( found ) {
    m_mgr.LoadPerspective( frames );
  }
  RemoveEmptyTabFrames();
  m_curpage = -1;
  SetSelection( sel_page );
  UpdateTabControlsArray();
  return true;
}

void M_Code_AuiNotebook::AllowScrolling( bool allow ) {
  s_AllowMousewheel = allow;
  for( size_t i = 0; i < s_cbAuiNotebookArray.GetCount(); ++i ) {
    s_cbAuiNotebookArray[i]->UpdateTabControlsArray();
    s_cbAuiNotebookArray[i]->ResetTabCtrlEvents();
  }
}

void M_Code_AuiNotebook::SetModKeys( wxString keys ) {
  s_modKeys = keys;
}

void M_Code_AuiNotebook::UseModToAdvance( bool use ) {
  s_modToAdvance = use;
}

void M_Code_AuiNotebook::InvertAdvanceDirection( bool invert ) {
  s_advanceDirection = invert ? -1 : 1;
}

void M_Code_AuiNotebook::InvertMoveDirection( bool invert ) {
  s_moveDirection = invert ? -1 : 1;
}
